using System;
using System.Collections.Generic;
using NobleTech.Products.CreatureKingdom.Utils;
using System.Drawing;

namespace NobleTech.Products.CreatureKingdom
{
    public class Organism
    {
        private IOrganism implementation;
        private OrganismAbilities abilities;
        private Move requestedMove;
        public double directionFacing;
        public IRenderer renderer;
        
        internal Quadrant Quadrant;

        public double Energy
        { get { return abilities.Energy; } }

        public string TypeName
        { get { return implementation.GetType().Name; } }

        public bool IsAlive
        { get { return abilities.Alive; } }

        
        private struct Interaction
        {
            public Move.Interaction.Types Type;
            public Organism Target;

            public Interaction(Move.Interaction.Types type, Organism target)
            {
                this.Type = type;
                this.Target = target;
            }
        }
        private List<Interaction> interactions = new List<Interaction>();

        public Organism(Guid id, Type implementationType, PointD location)
        {
            OrganismStats stats = OrganismLoader.GetOriginalStats(implementationType);
            implementation = OrganismLoader.CreateImplementation(implementationType, stats);
            abilities = new OrganismAbilities(id, implementationType, location, stats);
            requestedMove = new Move();
            renderer = OrganismLoader.CreateRenderer(implementationType, stats);            
        }

        public Organism(Organism mother, Organism father, uint id)
        {
            throw new NotImplementedException();
        }

        public Guid ID { get { return abilities.ID; } }
        public PointD Location { get { return abilities.Location; } }
        public double Size { get { return abilities.Size; } }
        public RectangleD Area { get { return new RectangleD(Location, Size); } }

        protected IEnumerable<OrganismInfo> GetSeenOrganisms()
        {
            double sightRange = abilities.SightRange + Size;
            // Get all organisms within a square that bounds the circle we can see in
            RectangleD sightBounds = new RectangleD(Location, sightRange);
            IEnumerable<Organism> seenOrgs = Quadrant.GetOrganisms(sightBounds, Size * 0.0005);
            // Find which organisms are not blocked, in our field of view and actually within range
            foreach (Organism org in seenOrgs)
            {
                if (org == this)
                    continue;
                SizeD toOrg = org.Location - Location;
                if (toOrg.Length > sightRange)
                    continue;
                yield return org.abilities.GetInfoCopy();
            }
        }

        protected double GetAngleTo(Organism target)
        {
            return Misc.NormaliseAngle((target.Location - Location).Angle - directionFacing);
        }

        protected bool IsAttacking(Organism target)
        {
            foreach (Interaction interaction in interactions)
            {
                if (interaction.Type == Move.Interaction.Types.Attacking && interaction.Target == target)
                    return true;
            }
            return false;
        }

        protected bool IsDefending(Organism target)
        {
            foreach (Interaction interaction in interactions)
            {
                if (interaction.Type == Move.Interaction.Types.Defending && interaction.Target == target)
                    return true;
            }
            return false;
        }

        public void GetMove()
        {
            // Clear the list of interactions for this move
            interactions.Clear();
            if (!abilities.Alive)
                return;
            implementation.DoMove(requestedMove, abilities.GetInfoCopy(), GetSeenOrganisms());
            // We are facing the direction we have specified, our target
            // or the direction we intend to move in that order
            if (requestedMove.DirectionFacing != null)
                directionFacing = requestedMove.DirectionFacing.Value;
            // You can't interact if giving birth
            if (abilities.GivingBirth)
                return;
            // Check the interactions and copy them into the internal list for this move
            uint interactionCount = 0;
            foreach (Move.Interaction requestedInteraction in requestedMove.Interactions)
            {
                if (interactionCount >= abilities.MaxInteractions)
                    break;
                ++interactionCount;
                // Only guys can do loving
                if (requestedInteraction.Type == Move.Interaction.Types.Loving && !abilities.CanDoLoving)
                    continue;
                // Check interaction is close enough
                double? range = null;
                switch (requestedInteraction.Type)
                {
                    case Move.Interaction.Types.Attacking:
                        range = abilities.AttackRange;
                        break;
                    case Move.Interaction.Types.Loving:
                        range = 0;
                        break;
                    case Move.Interaction.Types.Eating:
                        range = 0;
                        break;
                    case Move.Interaction.Types.Defending:
                        // Can defend against anyone that I can see
                        range = abilities.SightRange;
                        break;
                    case Move.Interaction.Types.Holding:
                        range = 0;
                        break;
                }
                Organism target = null;
                if (range != null)
                {
                    range += Size;
                    // Find the target, if it is roughly within range
                    foreach (Organism org
                        in Quadrant.GetOrganisms(new RectangleD(Location, range.Value), Size * 0.0005))
                    {
                        if (org.ID == requestedInteraction.Target)
                            target = org;
                    }
                }
                if (target != null)
                {
                    // Check that the target is actually within range
                    SizeD vectorToTarget = target.Location - Location;
                    if (vectorToTarget.Length > target.Size + range)
                        target = null;
                }
                if (target != null)
                    // Set the details in the OrganismInfo
                    interactions.Add(new Interaction(requestedInteraction.Type, target));
            }
            if (requestedMove.DirectionFacing == null)
                if (interactions.Count != 0)
                    directionFacing = (interactions[0].Target.Location - Location).Angle;
                else if (requestedMove.DirectionMoving != null)
                    directionFacing = requestedMove.DirectionMoving.Value;
        }

        /// <summary>
        /// Updates the info to show what each organism is singing and doing
        /// </summary>
        public void UpdateInfo()
        {
            if (!abilities.Alive)
                return;
            OrganismInfo info = abilities.Info;
            // Update the info to show roughly which direction each organism is facing
            // Round angle to an eighth of a turn
            //  so that this can't be used for unlimited precision communication
            const double directionUnit = 2 * Math.PI / 8;
            info.ApproximateDirectionFacing
                = Math.Round(directionFacing / directionUnit) * directionUnit;
            // There's nothing else you can do if giving birth
            if (abilities.GivingBirth)
                return;
            // Copy Move into publicly visible OrganismInfo
            info.HighNote = requestedMove.HighNote;
            info.LowNote = requestedMove.LowNote;
            // Copy the interactions
            foreach (Interaction interaction in interactions)
            {
                // If defending against someone who is attacking us then attack them
                if (interaction.Type == Move.Interaction.Types.Defending)
                {
                    if (!interaction.Target.IsAttacking(this))
                        // Don't show that we were defending unless actually being attacked
                        continue;
                    //SizeD vectorToTarget = interaction.Target.Location - Location;
                    //if (vectorToTarget.Length <= interaction.Target.Size + abilities.AttackRange + Size)
                    //    // Attack back otherwise leave as defending
                    //    interaction.Type = Move.Interaction.Types.Attacking;
                }
                // Set the details in the OrganismInfo
                info.AddInteraction(interaction.Type, interaction.Target.ID);
            }
        }

        public void DoInteraction()
        {
            foreach (Interaction interaction in interactions)
            {
                switch (interaction.Type)
                {
                    case Move.Interaction.Types.Attacking:
                        // Do attack
                        double defence = 0;
                        if (interaction.Target.IsAttacking(this) || interaction.Target.IsDefending(this))
                        {
                            // If the target is facing away then their defence is different
                            double defendingAngle = interaction.Target.GetAngleTo(this);
                            defence = interaction.Target.abilities.GetDefence(defendingAngle);
                        }
                        interaction.Target.abilities.Injured(abilities.Strength - defence);
                        break;
                    case Move.Interaction.Types.Loving:
                        // Do loving
                        interaction.Target.abilities.Impregnate(abilities);
                        break;
                }
            }
        }

        public event EventHandler Moved;
        public event EventHandler Grown;

        internal void DoMove()
        {
            if (!abilities.Alive || requestedMove.Speed <= 0
                || requestedMove.DirectionMoving == null || abilities.GivingBirth)
                return;
            double speed = Math.Min(requestedMove.Speed,
                abilities.MaxSpeed(directionFacing, requestedMove.DirectionMoving.Value));
            // Do the move
            abilities.SpendEnergy(speed * speed * Size);
            abilities.Move(SizeD.FromAngle(requestedMove.DirectionMoving.Value, speed));
            // Copy current location into Info
            abilities.Info.ApproximateLocation = new PointD(
                Math.Round(Location.X / (Size / 10)) * (Size / 10),
                Math.Round(Location.Y / (Size / 10)) * (Size / 10));
            // Fire the Moved event
            EventHandler moved = Moved;
            if (moved != null)
                moved(this, EventArgs.Empty);
        }

        public void DoLifeEvents()
        {
            if (!abilities.Alive)
                return;
            if (abilities.GivingBirth)
            {
                // TODO: Do giving birth
                throw new NotImplementedException();
                //abilities.SpendEnergy(???);
                /*abilities.EndPregnancy();
                if (!abilities.Alive)
                    return;*/
            }
            // Do aging
            abilities.Age();
            if (!abilities.Alive)
                return;
            // Do growth
            if (abilities.UpdateSize())
            {
                // Fire the Grown event
                EventHandler grown = Grown;
                if (grown != null)
                    grown(this, EventArgs.Empty);
                if (!abilities.Alive)
                    return;
            }
        }

    }
}
